package com.bq.server.configuration;

import com.bq.server.constant.DateFormatterConstant;
import com.bq.server.enumeration.Enumeration;
import com.bq.server.enumeration.EnumerationCache;
import com.bq.server.enumeration.ErrorCodeEnum;
import com.bq.server.properties.MybatisPlusSecurityProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.ResourceUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.concurrent.ConcurrentMap;

/**
 * Define AppConfig.
 * <p>Created with IntelliJ IDEA on 28/10/2017 14:30.</p>
 *
 * @author yangyanju [yangyanju@yxqiche.com]
 * @version 1.0
 */
@Configuration
@ConditionalOnWebApplication
@RequiredArgsConstructor
public class WebMvcConfiguration implements WebMvcConfigurer, DateFormatterConstant {


    private final MybatisPlusSecurityProperties securityProperties;

    /**
     * 跨域过滤
     */
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<>();
        registration.setName("corsFilter");
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 10);
        registration.addUrlPatterns("/*");
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        source.registerCorsConfiguration("/**", config);
        registration.setFilter(new CorsFilter(source));
        return registration;
    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer
                .favorPathExtension(false)
                .favorParameter(true)
                .parameterName("format")
                .ignoreAcceptHeader(false)
                .useRegisteredExtensionsOnly(true)
                .defaultContentType(MediaType.APPLICATION_JSON);
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        DateTimeFormatterRegistrar dateTime = new DateTimeFormatterRegistrar();
        //设置时间格式
        dateTime.setDateTimeFormatter(DEFAULT_DATE_TIME_FORMATTER);
        dateTime.setDateFormatter(DEFAULT_DATE_FORMATTER);
        dateTime.setTimeFormatter(DEFAULT_TIME_FORMATTER);
        //删除默认 java time 转换 此处只删除了LocalDateTime、LocalDate、LocalTime ,如果想覆盖其他默认值直接删除就可以了
        registry.removeConvertible(LocalDateTime.class, String.class);
        registry.removeConvertible(LocalDate.class, String.class);
        registry.removeConvertible(LocalTime.class, String.class);
        //注册 java time，
        dateTime.registerFormatters(registry);
        //添加枚举转换
        registry.addConverterFactory(new EnumerationConverterFactory<>());
    }

    @Bean
    @ConditionalOnClass(MappingJackson2HttpMessageConverter.class)
    @ConditionalOnMissingBean
    MappingJackson2HttpMessageConverter jackson2HttpMessageConverter(ObjectMapper mapper) {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(mapper);
        converter.setSupportedMediaTypes(ImmutableList.of(MediaType.TEXT_HTML, MediaType.APPLICATION_JSON));
        return converter;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    @ConditionalOnClass(CharacterEncodingFilter.class)
    @ConditionalOnMissingBean
    CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        filter.setForceEncoding(true);
        return filter;
    }


    /**
     * 枚举转换工厂
     *
     * @param <R>
     */
    public static class EnumerationConverterFactory<R extends Enumeration> implements ConverterFactory<String, R> {

        private final ConcurrentMap<Class<? extends R>, Converter<String, ? extends R>> cache = Maps.newConcurrentMap();

        @SuppressWarnings("unchecked")
        @Override
        public <T extends R> Converter<String, T> getConverter(Class<T> targetType) {
            Converter<String, ? extends R> converter = cache.computeIfAbsent(targetType, EnumerationConverter::new);
            return (Converter<String, T>) converter;
        }

        private static class EnumerationConverter<T extends Enumeration> implements Converter<String, T> {

            private final Class<T> enumClass;

            EnumerationConverter(Class<T> enumClass) {
                this.enumClass = enumClass;
            }

            @Override
            public T convert(String source) {
                T value = EnumerationCache.valueOf(enumClass, Integer.valueOf(source));
                if (value == null) {
                    ImmutableMap<Integer, Enumeration> enumValues = EnumerationCache.getEnumValues(enumClass);
                    throw ErrorCodeEnum.ERROR_CLIENT_ENUM_NOT_FOUND.businessException(source, enumValues.keySet());
                }
                return value;
            }
        }
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/img/**").addResourceLocations("file:" + securityProperties.getFolder());
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new PermissionInterceptor())
//                .addPathPatterns("/**")
                .excludePathPatterns("/**")
                .excludePathPatterns("/img/**");
    }
}
